#!/usr/bin/env python
# -*- coding: UTF-8 -*-

try:
	import pdb
	import pygame
	import sys
	from lib import utils
	from lib.animateable import Animateable
	from panel import *
	from pygame.locals import *
except ImportError, err:
	print "Could not load %s module." % (err)
	sys.exit(2)

'''
Creature

Any and all sprites that move and attack should be instances or
subclasses of this class.

Needs a lot of work. It's currently hardcoded to always face South.
'''

WALKING_PARAM = (48, 3)

class Creature(pygame.sprite.DirtySprite, Animateable):
	def __init__(self, x, y, layers, field, cache):
		pygame.sprite.DirtySprite.__init__(self)
		Animateable.__init__(self)

		#Sets test values for the Unit
		self.x = x + 48
		self.y = y + 48
		self.MV = 8
		self.atk_range = 5

		#Load unit image surface from cache and set transparency
		self.imgcache = cache.get(utils.ERWIN_3)
		self.rect = pygame.rect.Rect((self.x, self.y, 48, 48))
		self.image = pygame.surface.Surface(self.rect.size)
		self.redraw(utils.DIR_DOWN)
		self.image.set_colorkey(self.image.get_at((0,0)), RLEACCEL)

		#Sets initial animation Parameters
		self.dir = utils.DIR_DOWN
		self.walk_params = list(WALKING_PARAM)
		self.set_anim_params(48)
		self.mov_seq = 0
		self.frame = 0

		#Sets Initial misc. values
		self.moved = False
		self.focused = False
		self.selected = False
		self.move_range = [None]
		self.move = []

		#Loads Unit into LayeredSprites group
		self.layers = layers
		self.layers.add(self, layer = 1)

		#Loads Battlefield
		self.field = field

	def update(self):
		"""
		Updates the Creature every frame
		"""
		if self.is_animating():
			self.slide()
			self.rect.topleft = (self.x, self.y)

		self.walking_anim()

	def set_moved(self):
		"""
		Sets creature as moved
		"""
		self.moved = True
		self.redraw(utils.MOVED)

	def new_phase(self):
		"""
		Refreshes for new turn
		"""
		self.moved = False
		self.redraw()


	def walking_anim(self):
		"""
		Animates walking if self.move has tile positions to go in order

		I think it works fine? Not sure if blitting the image every frame is a smart thing though
		"""
		if not self.moved:
			if self.move != []:
				if self.walk_params[1] == 1:
					step = self.walk_params[0]
				else:
					step = int(ceil(self.walk_params[0] / self.walk_params[1]))

				x = (self.move[0][0] * 48) - (self.rect.left + self.field.get_tile_pos()[0] * 48) + 48
				y = (self.move[0][1] * 48) - (self.rect.top  + self.field.get_tile_pos()[1] * 48) + 48

				if x > 0:
					self.rect.left += step
					self.dir = utils.DIR_RIGHT
				elif x < 0:
					self.rect.left -= step
					self.dir = utils.DIR_LEFT
				elif y > 0:
					self.rect.top += step
					self.dir = utils.DIR_DOWN
				elif y < 0:
					self.rect.top -= step
					self.dir = utils.DIR_UP

				self.walk_params[0] -= step
				self.walk_params[1] -= 1

				self.redraw(self.dir, self.mov_seq)

				if not self.walk_params[0]:
					self.walk_params = list(WALKING_PARAM)
					self.move.pop(0)
				self.dirty = 1

			if self.focused == True or self.selected == True:
				# every 20 frames, rest on the other foot
				self.frame = (self.frame + 1) % 20
				if self.frame == 0:
					self.mov_seq  = (self.mov_seq + 1) % 2
					self.redraw(self.dir, self.mov_seq)
					self.dirty = 1

	def select(self):
		"""
		Toggles selected - only used to animate if not focused now

		Redundant if can't focus on others when selected
		"""
		self.selected = not self.selected
		return self

	def redraw(self, dir = utils.DIR_DOWN, anim = 0):
		"""
		Simple fill and blit to cover transparency
		"""
		self.image.fill((0,199,0))
		self.image.blit(self.imgcache.get(dir, anim), (0,0))

	def focus(self):
		"""
		Toggles focus(ie. hover over selection) of unit for walking animation
		"""
		if not self.moved:
			if self.focused == True:
				self.focused = False
				self.redraw(utils.DIR_DOWN, 0)
				self.mov_seq = 0
			else:
				self.focused = True
				self.redraw(utils.DIR_DOWN, 1)
				self.mov_seq = 1
			self.dirty = 1
		return self


	def c_move(self, key, mod):
		'''
		c_move() is NOT walk()
		c_move() moves the sprite, walk() makes the creature walk (eventually)

		We can cheat and simplify this method, since we know this
		object never moves unless the Battlefield does.
		'''
		self.mod = mod
		if key in (K_UP, K_RIGHT, K_DOWN, K_LEFT): return key
		else: return 0

	def r_move(self, key, mod):
		"""
		Reverse Move

		Called when Battlefield is scrolled etc.
		"""
		moved = self.c_move(key, mod)
		if moved:
			if key == K_UP: self.animating[1] = moved and K_DOWN
			elif key == K_RIGHT: self.animating[0] = moved and K_LEFT
			elif key == K_DOWN: self.animating[1] = moved and K_UP
			elif key == K_LEFT: self.animating[0] = moved and K_RIGHT
		return key

	def walk(self, dest):
		"""
		Sets walk to destination

		Creates the move path list, remembers old_pos for canceling, sets highlighting
		"""
		overlay = self.layers.get_sprites_from_layer(2)[0]
		move = self.get_move_list(dest)
		overlay.set_highlight(move)
		self.old_pos = self.rect.topleft
		self.move = move[1:]


	def get_tile_pos(self):
		"""
		Returns Tile Position of unit in overall battlefield
		"""
		x = self.rect.left / 48 + self.field.get_tile_pos()[0] - 1
		y = self.rect.top / 48 + self.field.get_tile_pos()[1] - 1
		return (x, y)

	def confirm_move(self):
		"""
		Confirms movement command

		Updates unit array in units layer, also resets this unit. Returns false
		if unit still in process of moving

		Add turn finished!
		"""
		if self.move == []:
			overlay = self.layers.get_sprites_from_layer(2)[0]
			x, y = self.old_pos
			x = (x / 48) + self.field.get_tile_pos()[0] - 1
			y = (y / 48) + self.field.get_tile_pos()[1] - 1

			self.x, self.y = self.rect.topleft

			new_x, new_y = self.get_tile_pos()

			# first remove unit from old pos, then add to new pos
			# because if old == new, the unit disappears logically if not visually
			overlay[x][y] = None
			overlay[new_x][new_y] = self

			self.dir = utils.DIR_DOWN
			self.redraw(utils.DIR_DOWN, 1)
			self.walk_params = list(WALKING_PARAM)
			self.dirty = 1

			self.moved = True
			return True
		return False


	def command(self, type):
		"""
		Generic command called from creature menu
		"""
		cursor = self.layers.get_sprites_from_layer(6)[0]
		overlay = self.layers.get_sprites_from_layer(2)[0]

		if type == "Attack":
			cursor.set_mode("Attack")
			overlay.set_highlight(self.get_attack_range())
			overlay.select_unit(self)
		elif type == "Move":
			cursor.set_mode("Move")
			overlay.set_highlight(self.get_move_range())
			overlay.select_unit(self)


	def get_move_range(self):
		"""
		Returns a list of possible movement tiles (tile co-ords)

		Uses an implementation of Dijkstra's Algorithm
		"""

		pos = self.get_tile_pos()

		if pos == self.move_range[0]:
			return self.move_range

		#list of open nodes [0] is pos, [1] is cost to get there
		open = [(pos, 0)]

		#list of closed nodes [0] is pos, [1] is cost to get there
		closed = [(pos, 0)]

		#list of selected nodes
		selected = []
		selected.append(pos)

		bounds = self.field.get_tile_size()
		overlay = self.layers.get_sprites_from_layer(2)[0]

		while open != []:
			#takes last open node
			open.sort(lambda x, y: cmp(y[1], x[1]))
			(x, y), cost = open.pop()

			#gets all neighbours of that node
			for (nx, ny) in self.get_neighbours(x, y):

				#gets cost to travel onto that neighbour
				new_cost = self.field[nx][ny].move

				#if impassable, ignore
				if new_cost != -1 and 0 <= nx < bounds[0] and \
						0 <= ny < bounds[1]:

					#calculates new total cost to get to neighbour
					new_cost += cost

					zip1, zip2 = zip(*closed)
					#if node is already closed
					if (nx, ny) in zip1:
						i = list(zip1).index((nx,ny))

						#check if new cost is less then before and moveable
						if new_cost < closed[i][1] and new_cost <= self.MV:
							#removes closed from closed, adds to open, selected
							closed.pop(i)
							open.append((closed[i][0], new_cost))
							if (nx, ny) not in selected:
								selected.append((nx,ny))

					#if node is not closed already
					else:
						#if node moveable, add to open + selected
						if new_cost <= self.MV:
							if (nx, ny) not in selected:
								selected.append((nx, ny))
								open.append(((nx, ny), new_cost))
						#if node not moveable, add to closed
						else:
							closed.append(((nx, ny), new_cost))



		self.move_range = selected
		return selected

	def cancel(self):
		"""
		Cancels command

		Resets unit back to original position and parameters
		"""
		self.rect.topleft = self.old_pos
		self.move = []
		self.dir = utils.DIR_DOWN
		self.redraw(utils.DIR_DOWN, 1)
		self.walk_params = list(WALKING_PARAM)
		self.dirty = 1

	def get_move_list(self, dest):
		"""
		Messy A* like algorithm to find route to destination tile
		"""
		pos = self.get_tile_pos()
		adist = abs(dest[0] - pos[0]) + abs(dest[1] - pos[1])
		open = [[adist, 0, pos]]

		while True:
			open.sort(lambda x, y: cmp(y[0], x[0]))
			path = open.pop()

			if path[0] == 0:
				return path[2:]

			x, y = path[-1]
			cost = path[1]

			for (nx, ny) in self.get_neighbours(x, y):
					if (nx, ny) in self.move_range:
						new_cost = self.field[nx][ny].move

						if new_cost != -1:
							new_cost += cost

							if new_cost <= self.MV:
								dist = abs(dest[0] - nx) + abs(dest[1] - ny)
								new_path = path[:]
								new_path[0] = dist
								new_path[1] = new_cost
								new_path.append((nx, ny))
								open.append(new_path)


	def get_neighbours(self, x, y):
		"""
		Returns the four neighbour co-ords (tile co-ords)
		"""
		return ((x+1, y),(x-1, y),(x, y+1),(x, y-1))


	def get_attack_range(self):
		"""
		Calculates potential attack range (No terrain cost involved)
		"""
		bounds = self.field.get_tile_size()
		pos = self.get_tile_pos()
		ox, oy = pos

		open = [pos]
		selected = [pos]
		closed = []

		while open != []:
			x, y = open.pop()
			for (nx, ny) in self.get_neighbours(x, y):
				if (nx, ny) not in closed:
					cost = abs(nx - ox) + abs(ny - oy)
					if cost <= self.atk_range and 0 <= nx < bounds[0]\
						and 0 <= ny < bounds[1]:
						selected.append((nx, ny))
						closed.append((nx, ny))
						open.append((nx, ny))
					else:
						closed.append((nx, ny))

		return selected


'''
These are some properties that a creature should have

	self.HP = 10
	self.AT = 0
	self.DF = 0
	self.MV = 3
	self.magic_AT = 0
	self.magic_DF = 0
	self.atk_range = 1
	self.cmd_range = 2

	self.magics = ()
	self.summons = ()
	self.is_commander = 0

	self.has_acted = 0
	self.tactic = 0

	self.facing = SOUTH
'''
